/**
 *js 虚拟机核心
 */
package jsvmcore

import (
	"errors"
	"log"
	"os"
	"strings"
	"time"

	"github.com/dop251/goja"
	"github.com/dop251/goja_nodejs/require"
)

type (
	JsVM struct {
		Debug                       bool
		JSFileMainKey, JSFileSubKey string
		State                       *goja.Runtime
	}
	//自定义动作方法定义
	JsVmFn   func(params ...interface{}) goja.Value
	HandleFn func(vm *JsVM) JsVmFn
)

var (
	golobalFns     = map[string]HandleFn{}
	golobalObjects = map[string]map[string]HandleFn{}
)

// RegisterFn 允许外部修改默认行为
func RegisterFn(name string, fn HandleFn) {
	golobalFns[name] = fn
}

// 注册对象
func RegisterObject(name string, methods map[string]HandleFn) {
	golobalObjects[name] = methods
}

// 使用加工后的key对内容进行异或加密
func xorEncrypt(content, key []byte) []byte {
	if len(key) == 0 {
		return content
	}

	result := make([]byte, len(content))
	keyLen := len(key)

	for i := 0; i < len(content); i++ {
		result[i] = content[i] ^ key[i%keyLen]
	}

	return result
}

// 错误拦截
func Catch() func() {
	return func() {
		if err := recover(); err != nil {
			log.Println("jsvm panic::", err)
		}
	}
}

// 创建vm对象
func NewJsVM(mainKey, subKey string, debug bool) *JsVM {
	return &JsVM{JSFileMainKey: mainKey, JSFileSubKey: subKey, Debug: debug}
}

// javascript文件加载，对ds扩展名解密
func (vm *JsVM) customJsSourceFileLoader(path string) ([]byte, error) {
	//TODO 加载内容
	if _, err := os.Stat(path); err != nil {
		return nil, err
	} else {
		bs, err := os.ReadFile(path)
		if err != nil {
			return nil, err
		}
		if strings.HasSuffix(path, ".ds") {
			head := bs[0:32]
			body := bs[32:]
			head = xorEncrypt(head, []byte(vm.JSFileSubKey))
			body = xorEncrypt(body, head)
			return body, nil
		} else {
			return bs, nil
		}
	}
}

// Destory 资源释放
func (vm *JsVM) Destory() {
	if vm.State != nil {
		vm.State = nil
	}
}

// ForceDestory 强制资源释放
func (vm *JsVM) ForceDestory() {
	if vm.State != nil {
		vm.State.Interrupt(errors.New("强制中断脚本执行")) //发送中断指令
		time.Sleep(5 * time.Second)
		vm.State = nil
	}
}

// 加载脚本，解释脚本。要执行定义的函数，需要调用RunMethod函数
func (vm *JsVM) LoadScript(script string, ch chan<- struct{}) error {
	defer func() {
		Catch()
		ch <- struct{}{}
	}()
	//创建LUA执行环境
	vm.State = goja.New()
	debugger := NewDebugger(vm)
	//使用自定义的文件加载机制
	registry := require.NewRegistryWithLoader(vm.customJsSourceFileLoader)
	registry.Enable(vm.State)
	//函数绑定
	for k, v := range golobalFns {
		vm.State.Set(k, v(vm))
	}
	//对象绑定，更符合面向对象开发
	for k, v := range golobalObjects {
		obj := map[string]interface{}{}
		for _k, _v := range v {
			obj[_k] = _v(vm)
		}
		vm.State.Set(k, obj)
	}
	// 绑定debug,注入调试函数
	vm.State.Set("debug", map[string]interface{}{
		"breakpoint": func(call goja.FunctionCall) goja.Value {
			line := call.Argument(0).ToInteger()
			debugger.CheckBreakpoint(int(line))
			return nil
		},
	})

	//脚本中都是封装的函数
	_, err := vm.State.RunString(script)
	return err
}

// 加载脚本，设定超时
func (vm *JsVM) LoadScriptWithTimeout(script string, timeout time.Duration) (err error) {
	var ch = make(chan struct{}, 1)
	go vm.LoadScript(script, ch)
	for {
		select {
		case <-time.After(timeout):
			vm.ForceDestory()
			return errors.New("timeout")
		case <-ch:
			return nil
		}
	}
}

// 加载脚本，设定超时
func (vm *JsVM) LoadJsFileWithTimeout(file string, timeout time.Duration) (err error) {
	var ch = make(chan struct{}, 1)
	bs, err := vm.customJsSourceFileLoader(file)
	if err != nil {
		return err
	}
	go vm.LoadScript(string(bs), ch)
	for {
		select {
		case <-time.After(timeout):
			vm.ForceDestory()
			return errors.New("timeout")
		case <-ch:
			return nil
		}
	}
}

// 执行脚本中的函数
func (vm *JsVM) RunMethod(method string, paramFn func(state *goja.Runtime) (param []goja.Value), ch chan<- struct{}) error {
	defer func() {
		Catch()
		ch <- struct{}{}
	}()
	//创建LUA执行环境
	if vm.State == nil {
		return errors.New("javascript 运行环境未初始化")
	}
	fn, ok := goja.AssertFunction(vm.State.Get(method))
	if !ok {
		return errors.New("main not fount")
	}
	_, err := fn(goja.Undefined(), paramFn(vm.State)...)
	return err
}

// 执行脚本中的函数，设定超时
func (vm *JsVM) RunMethodWithTimeout(method string, paramFn func(state *goja.Runtime) (param []goja.Value), timeout time.Duration) (err error) {
	var ch = make(chan struct{}, 1)
	go vm.RunMethod(method, paramFn, ch)
	for {
		select {
		case <-time.After(timeout):
			vm.ForceDestory()
			return errors.New("timeout")
		case <-ch:
			return nil
		}
	}
}

// 加密js文件
func (vm *JsVM) EncryptJsFile(src, dst, subkey string) error {
	if len(subkey) != 32 {
		return errors.New("key长度不够32位")
	}
	encryptKey := xorEncrypt([]byte(subkey), []byte(vm.JSFileMainKey)) //真正密钥
	srcBs, err := os.ReadFile(src)
	if err != nil {
		return err
	}
	body := encryptKey[:]
	body = xorEncrypt(body, []byte(vm.JSFileSubKey))
	body = append(body, xorEncrypt(srcBs, encryptKey)...)
	return os.WriteFile(dst, body, 0777)
}

// 解密ds文件,只需要知道主密钥即可
func (vm *JsVM) DecryptJsFile(src, dst string) error {
	bs, err := os.ReadFile(src)
	if err != nil {
		return err
	}
	head := bs[0:32]
	body := bs[32:]
	encryptKey := xorEncrypt(head, []byte(vm.JSFileSubKey))
	body = xorEncrypt(body, encryptKey)
	return os.WriteFile(dst, body, 0777)
}
